/* Zatok - язык программирования текстовых игр
 * (С) 2010 - 2012 Меркулов Матвей Юрьевич
 * Код лицензирован под лицензией GNU LGPL
 * Вы можете прочитать текст лицензии по адресу:
 * http://www.gnu.org/copyleft/lesser.html
 * или прочитать русский перевод по адресу:
 * http://ru.wikisource.org/wiki/GNU_Lesser_General_Public_License
 */

package parser;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.ListIterator;

public class CCase {
	private static HashMap<String, String[]> Cases = new HashMap<String, String[]>();
	private static HashMap<String, LinkedList<CEndingsPack>> Endings = new HashMap<String, LinkedList<CEndingsPack>>();

	public enum EGender { Undefined, Masculine, Feminine, Neuter, Plural };
	public enum EAnimated{ Undefined, Unanimated, Animated };



	public static void AddWordCases( LinkedList<String> Tokens ) {
		ListIterator<String> TokensIterator = Tokens.listIterator( 1 );
		while (true) {
			if ( !TokensIterator.hasNext() ) CParser.Error();
			String Base = TokensIterator.next();
			String WordCases[] = new String[ 7 ];
			EGender Gender = EGender.Undefined;
			EAnimated Animated = EAnimated.Undefined;
			while (true) {
				String Separator = TokensIterator.next();
				if ( Separator.equals( "+" ) ) {
					for ( int CaseNum = 0; CaseNum < 7; CaseNum++ ) {
						if ( !TokensIterator.hasNext() ) CParser.Error();
						String Ending = TokensIterator.next();
						if ( Ending.equals( "_" ) ) {
							WordCases[ CaseNum ] = Base;
						} else {
							WordCases[ CaseNum ] = Base + Ending;
						}
						if ( CaseNum < 6 ) {
							if ( !TokensIterator.hasNext() ) CParser.Error();
							if ( !TokensIterator.next().equals( "/" ) ) CParser.Error();
						}
					}
					break;
				} else {
					if ( !Separator.equals(  "(" ) ) CParser.Error();
					if ( !TokensIterator.hasNext() ) CParser.Error();
					String Option = TokensIterator.next();
					Gender = GetGender( Option, Gender );
					if( Gender == EGender.Undefined ) {
						if( Option.equals( "неодуш" ) ) {
							Animated = EAnimated.Unanimated;
						} else if ( Option.equals( "одуш" ) ) {
							Animated = EAnimated.Animated;
						} else {
							CParser.Error( Tokens.get( 1 ) + " - ожидается род / число" );
						}
					}
					if ( !TokensIterator.hasNext() ) CParser.Error();
					if ( !TokensIterator.next().equals( ")" ) ) CParser.Error();
				}

				if ( !TokensIterator.hasNext() || TokensIterator.next().equals( "," ) ) {
					for ( int CaseNum = 0; CaseNum < 7; CaseNum++ ) {
						WordCases[ CaseNum ] = ChangeWordCase( Base, CaseNum, Gender, Animated );
					}
					break;
				}
			}
			Cases.put( WordCases[ 0 ], WordCases );
			if ( !TokensIterator.hasNext() ) break;
		}
	}



	public static LinkedList<String> DeclinedNames( String Name, boolean WithSpaces ) {
		LinkedList<String> DeclinedNamesList = new LinkedList<String>();
		LinkedList<CWord> WordList = ToWordList( Name );

		// занесение склонений названия
		for ( int CaseNum = 0; CaseNum < 12; CaseNum++ ) {
			String DeclinedName = "";
			for ( CWord Word : WordList ) {
				if ( WithSpaces && !DeclinedName.isEmpty() ) DeclinedName += " ";
				if ( Word.Decline ) {
					if( CaseNum >= 7 ) {
						String PluralWord = ChangeWordCase( Word.Name, 6 );
						if( PluralWord != null ) DeclinedName += ChangeWordCase( PluralWord, CaseNum - 6, EGender.Plural, EAnimated.Undefined );
					} else {
						DeclinedName += ChangeWordCase( Word.Name, CaseNum );
					}
				} else {
					DeclinedName += Word.Name;
				}
			}
			DeclinedNamesList.add( DeclinedName );
		}

		return DeclinedNamesList;
	}



	private static LinkedList<CWord> ToWordList( String Phrase ) {
		LinkedList<CWord> WordList = new LinkedList<CWord>();
		String Word = "";
		boolean Decline = true;
		for ( int N = 0; N < Phrase.length(); N++ ) {
			Character Char = Phrase.charAt( N );
			if ( Char == '{' ) {
				AddWord( WordList, Word, Decline );
				Word = "";
				Decline = false;
			} else if ( Char == '}' ) {
				AddWord( WordList, Word, Decline );
				Word = "";
				Decline = true;
			} else if ( Character.isDigit( Char ) || Char == '_' ) {
				AddWord( WordList, Word, Decline );
				AddWord( WordList, Character.toString( Char ), false );
				Word = "";
			} else if( Character.isUpperCase( Char ) ) {
				AddWord( WordList, Word, Decline );
				Word = Character.toString( Phrase.charAt( N ) ).toLowerCase();
			} else {
				Word += Phrase.charAt( N );
			}
		}
		if ( !Word.isEmpty() ) AddWord( WordList, Word, Decline );
		return WordList;
	}



	private static void AddWord( LinkedList<CWord> WordList, String WordName, boolean Decline ) {
		if ( !WordName.isEmpty() ) {
			CWord Word = new CWord();
			Word.Name = WordName;
			Word.Decline = Decline;
			WordList.add( Word );
		}
	}



	public static String ChangeTextCase( String Text, int CaseNum ) {
		LinkedList<CWord> WordList = ToWordList( Text );
		
		String DeclinedText = "";
		for ( CWord Word : WordList ) {
			if ( !DeclinedText.isEmpty() ) DeclinedText += " ";
			if ( Word.Decline ) {
				if( CaseNum >= 7 ) {
					String PluralWord = ChangeWordCase( Word.Name, 6 );
					if( PluralWord != null ) DeclinedText += ChangeWordCase( PluralWord, CaseNum - 6, EGender.Plural, EAnimated.Undefined );
				} else {
					DeclinedText += ChangeWordCase( Word.Name, CaseNum );
				}
			} else {
				DeclinedText += Word.Name;
			}
		}
		return DeclinedText;

		/*
		if ( Case == 0 ) return Text;
		Text += " ";
		String NewText = "", Word = "";
		for ( int N = 0; N < Text.length(); N++ ) {
			if ( Text.charAt( N ) == ' ' ) {
				if ( !NewText.isEmpty() ) NewText += " ";
				NewText += ChangeWordCase( Word, Case );
				Word = "";
			} else {
				Word += Text.charAt( N );
			}
		}
		return NewText;
		*/
	}



	public static String ChangeWordCase( String Word, int Case, EGender Gender, EAnimated Animated ) {
		if ( Case == 0 || Character.isDigit( Word.charAt( 0 ) ) ) return Word;
		String[] WordArray = Cases.get( Word );
		if ( WordArray == null ) {
			int EndingStart = Word.length() - 5;
			if ( EndingStart < 0 ) EndingStart = 0;
			while (true) {
				String Ending = Word.substring( EndingStart );

				LinkedList<CEndingsPack> EndingsPackList = Endings.get( Ending );
				if ( EndingsPackList != null ) {
					for ( CEndingsPack EndingsPack : EndingsPackList ) {
						if ( Gender == EGender.Undefined || EndingsPack.Gender == Gender ) {
							if ( Case == 3 ) {
								if ( Animated == EAnimated.Unanimated ) return Word.substring( 0, EndingStart ) + EndingsPack.WordEndings[ 0 ];
								if ( Animated == EAnimated.Animated ) return Word.substring( 0, EndingStart ) + EndingsPack.WordEndings[ 1 ];
							}
							return Word.substring( 0, EndingStart ) + EndingsPack.WordEndings[ Case ];
						}
					}
				}

				if ( EndingStart == Word.length() ) break;
				EndingStart ++;
			}
			return Word;
		} else {
			return WordArray[ Case ];
		}
	}



	public static String ChangeWordCase( String Word, int Case ) {
		return ChangeWordCase( Word, Case, EGender.Undefined, EAnimated.Undefined );
	}



	public static void Init() {
		CReader Reader = new CReader( "libs/endings.txt" );
		CParser.CurrentReader = Reader;

		while( !CParser.CurrentReader.EOF ) {
			LinkedList<String> Tokens = CParser.ToTokenList( Reader.ReadLine() );
			if( Tokens.isEmpty() ) continue;
			
			char FirstLetters[] = null;
			if ( Tokens.getFirst().startsWith( "\"" ) ) {
				FirstLetters = CParser.ExtractString( Tokens.getFirst() ).toCharArray();
				Tokens.removeFirst();
			}

			char SecondLetters[] = null;
			if ( Tokens.getFirst().startsWith( "\"" ) ) {
				SecondLetters = CParser.ExtractString( Tokens.getFirst() ).toCharArray();
				Tokens.removeFirst();
			}

			EGender Gender = EGender.Undefined;
			if ( Tokens.size() == 8 ) {
				Gender = GetGender( Tokens.getLast(), Gender );
				if( Gender == EGender.Undefined ) CParser.Error( Tokens.getFirst() + " - ожидается род / число" );
				Tokens.removeLast();
			}
			if ( Tokens.size() != 7 ) CParser.Error( Tokens.getFirst() + " - должно быть 7 окончаний" );

			if ( FirstLetters != null ) {
				for( char FirstLetter : FirstLetters ) {
					if ( SecondLetters != null ) {
						for( char SecondLetter : SecondLetters ) {
							SetEndings( Tokens, Gender, String.valueOf( FirstLetter ) + String.valueOf( SecondLetter ) );
						}
					} else {
						SetEndings( Tokens, Gender, String.valueOf( FirstLetter ) );
					}
				}
			} else {
				SetEndings( Tokens, Gender, "" );
			}
		}

		Reader.Close();
		
		Reader = new CReader( "libs/exceptions.txt" );
		CParser.CurrentReader = Reader;
		
		while( !CParser.CurrentReader.EOF ) {
			String[] Chunks = Reader.ReadLine().split( " " );
			switch( Chunks.length ) {
				case 1:
					break;
				case 2:
					if ( Chunks[ 1 ].equals( "муж" ) ) {
						AddWordException( Chunks[ 0 ], EGender.Masculine, EAnimated.Undefined, null );
					} else if ( Chunks[ 1 ].equals( "жен" ) ) {
						AddWordException( Chunks[ 0 ], EGender.Feminine, EAnimated.Undefined, null );
					} else if ( Chunks[ 1 ].equals( "ср" ) ) {
						AddWordException( Chunks[ 0 ], EGender.Neuter, EAnimated.Undefined, null );
					} else if ( Chunks[ 1 ].equals( "мн" ) ) {
						AddWordException( Chunks[ 0 ], EGender.Plural, EAnimated.Undefined, null );
					} else if( Chunks[ 1 ].equals( "неодуш" ) ) {
						AddWordException( Chunks[ 0 ], EGender.Undefined, EAnimated.Unanimated, null );
					} else if ( Chunks[ 1 ].equals( "одуш" ) ) {
						AddWordException( Chunks[ 0 ], EGender.Undefined, EAnimated.Animated, null );
					} else {
						CParser.Error();
					}
					break;
				case 8:
					String[] Variants = new String[ 7 ];
					for( int N = 0; N < 7; N++ ) Variants[ N ] = Chunks[ 0 ] + Chunks[ N + 1  ].replace( "_", "" );
					AddWordException( Variants[ 0 ], EGender.Undefined, EAnimated.Undefined, Variants );
					break;
				default:
					CParser.Error( "Ожидается 2 или 8 слов" );
			}
		}
		
		Reader.Close();

		/*Reader = new CReader( "D:/projects/dics/Z_160" );
		while( true ) {
			String Line = Reader.ReadLine();
			if ( Reader.EOF ) break;
			int SpacePos = Line.indexOf( ' ' );
			String Word = Line.substring( 0, SpacePos );
			String NewLine = "";
			for ( int N = 0; N < 6; N++ ) {
				switch ( N ) {
					case 0:
						NewLine = ChangeWordCase( Word, N );
						break;
					case 1:
						NewLine += ", нет " + ChangeWordCase( Word, N );
						break;
					case 2:
						NewLine += ", дать " + ChangeWordCase( Word, N );
						break;
					case 3:
						NewLine += ", винить " + ChangeWordCase( Word, N );
						break;
					case 4:
						NewLine += ", вертеть " + ChangeWordCase( Word, N );
						break;
					case 5:
						NewLine += ", о " + ChangeWordCase( Word, N );
						break;
				}
			}
			System.out.println( NewLine );
		}
		Reader.Close();

		System.exit( 0 );*/
	}



	private static void SetEndings( LinkedList<String> Tokens, EGender Gender, String Prefix ) {
		CEndingsPack EndingsPack = new CEndingsPack();
		EndingsPack.Gender = Gender;
		EndingsPack.WordEndings = new String[ 7 ];
		Tokens.toArray( EndingsPack.WordEndings );
		for( int N = 0; N < 7; N++ ) {
			if ( EndingsPack.WordEndings[ N ].equals( "_" ) ) EndingsPack.WordEndings[ N ] = "";
			EndingsPack.WordEndings[ N ] = Prefix + EndingsPack.WordEndings[ N ];
		}

		LinkedList<CEndingsPack> EndingsPackList = Endings.get( EndingsPack.WordEndings[ 0 ] );
		if ( EndingsPackList == null ) EndingsPackList = new LinkedList<CEndingsPack>();
		EndingsPackList.add( EndingsPack );
		Endings.put( EndingsPack.WordEndings[ 0 ], EndingsPackList );
	}



	public static void Clear() {
		Cases.clear();
	}
	
	
	
	private static EGender GetGender( String Option, EGender OldGender ) {
		if ( Option.equals( "муж" ) ) {
			return EGender.Masculine;
		} else if ( Option.equals( "жен" ) ) {
			return EGender.Feminine;
		} else if ( Option.equals( "ср" ) ) {
			return EGender.Neuter;
		} else if ( Option.equals( "мн" ) ) {
			return EGender.Plural;
		} else {
			return OldGender;
		}
	}
	
	
	
	private static void AddWordException( String Name, EGender Gender, EAnimated Animated, String[] Variants ) {
		if( Variants == null ) {
			Variants = new String[ 7 ];
			for( int N = 0; N < 7; N++ ) Variants[ N ] = ChangeWordCase( Name, N, Gender, Animated );
		}
		Cases.put( Variants[ 0 ], Variants );
	}
}